﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using IndicoInterface.SimpleAgendaDataModel;
using System.Threading;
using Microsoft.Office.Interop.PowerPoint;
using Microsoft.Office.Core;
using Utilities;

namespace DTBuilderLib
{
    /// <summary>
    /// This class renders the input files into jpeg's.
    /// </summary>
    public class JPEGRender
    {
        public int DotsPerInch { get; set; }

        public JPEGRender()
        {
            DotsPerInch = 600;
        }

        /// <summary>
        /// Given the path to a PDF file, render it in place.
        /// </summary>
        /// <param name="talkPath">Path to talk to render as JPEG</param>
        /// <returns>List of rendered JPEG's - full path to them</returns>
        public List<FileInfo> RenderTalk(string talkPath)
        {
            ///
            /// Do a few sanity checks
            /// 

            if (talkPath == null || talkPath == "")
            {
                return new List<FileInfo>();
            }

            FileInfo talkInfo = new FileInfo(talkPath);
            if (!talkInfo.Exists)
            {
                return new List<FileInfo>();
            }

            ///
            /// Now, depending on what we see, dispatch the rendering
            /// 

            try
            {
                string extension = talkInfo.Extension.ToUpper();
                if (extension == ".PDF")
                {
                    return RenderPDF(talkInfo);
                }
                else if (extension == ".PS")
                {
                    return RenderPS(talkInfo);
                }
                else if (extension == ".PPT" || extension == ".PPTX")
                {
                    return RenderPPT(talkInfo);
                }
            }
            catch (Exception e)
            {
                Logger.LogErrorMessage("RenderTalkExceptoin", "Exception occured rendering talk (" + talkInfo.FullName + "): " + e.Message + " - " + e.StackTrace);
                return new List<FileInfo>();
            }

            ///
            /// If we are here, then we have download a file we don't know what to do with. That isn't good!!
            /// 

            Utilities.Logger.LogErrorMessage("BadFileType", "Don't know how to render file '" + talkInfo.FullName + "' to JPEG!");
            return new List<FileInfo>();            
        }

        /// <summary>
        /// Render a postscript talk. We have to be special here because ghostview, which
        /// does the rendering, doesn't seem to always pay attention to the orientation.
        /// We assume, btw, that the orientation is the same through out the talk.
        /// </summary>
        /// <param name="talkInfo"></param>
        /// <returns></returns>
        private List<FileInfo> RenderPS(FileInfo talkInfo)
        {
            ///
            /// Convert to jpeg, and take the easy way out.
            /// 

            List<FileInfo> result = RenderWithGS(talkInfo);
            if (result.Count == 0)
            {
                return result;
            }

            ///
            /// To guess the orientation, we have to do a little work here. The main problem is
            /// that how PostScript files put things together is a mixed bag.
            /// 
            /// We look for two clues. First, the BoundingBox - if that has x longer than y, then
            /// we can "assume" we are dealing with a landscaped file. If the Orientation comment exists,
            /// and is marked landscape, we again make the same assumption.
            /// 

            var psCommentLines = talkInfo.GetLineIterator().TakeWhile(ln => !ln.Contains("%%EndComments"));
            var orientLines = from l in psCommentLines
                              where l.Contains("Orientation") || l.Contains("BoundingBox")
                              select l;

            bool isLandscapeBB = false;
            bool isLandscapeOr = false;

            foreach (var l in orientLines)
            {
                if (l.Contains("Orientation") && l.Contains("Landscape"))
                {
                    isLandscapeOr = true;
                }
                if (l.Contains("BoundingBox"))
                {
                    var splitinfo = l.Split(' ');
                    var allints = from i in l.Split(' ')
                                  where isOnlyNumbers(i)
                                  select Convert.ToInt32(i);

                    var allintsa = allints.ToArray();
                    if (allintsa.Length == 4)
                    {
                        int deltax = allintsa[2] - allintsa[0];
                        int deltay = allintsa[3] - allintsa[1];

                        isLandscapeBB = deltax > deltay;
                    }
                }
            }

            bool isLandscape = isLandscapeBB || isLandscapeOr;
#if false
            ///
            /// Ghost script will have done portrait. Look at the source ps file to
            /// see if the orientation is land-scape.
            /// 

            var psCommentLines = talkInfo.GetLineIterator().TakeWhile(ln => !ln.Contains("%%EndComments"));
            var orientLines = from l in psCommentLines
                                where l.Contains("Orientation")
                                select l;

            bool isLandscape = false;
            if (orientLines.Count() > 0)
            {
                isLandscape = orientLines.First().Contains("Landscape");
            }
#endif

            ///
            /// Now, check out which orientation it is in. See if it matches.
            /// 

            if (isLandscape != Image.IsLandscape(result[0]))
            {
                ///
                /// We rotate one direction for all ps except that generated by latex's dvips. :(
                /// 

                bool rotate90 = true;
                var dvilines = from l in psCommentLines
                               where l.Contains("Creator")
                               select l;
                if (dvilines.Count() > 0)
                {
                    rotate90 = !dvilines.First().Contains("dvips");
                }

                foreach (var image in result)
                {
                    Image.RotateInPlace(image, rotate90);
                }
            }

            return result;

        }

        /// <summary>
        /// Returns true if only the integer numbers are in this string.
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        private bool isOnlyNumbers(string i)
        {
            var badchars = from c in i
                           where !char.IsDigit(c)
                           select c;
            return badchars.Count() == 0;
        }

        /// <summary>
        /// Render a powerpoint file to PDF, and then to jpeg. Requires all sorts of PPT stuff
        /// to be installed on this machine!
        /// Unfortunately, we can only do one of these at a time... Hopefully this is faster than
        /// what comes after this! :-)
        /// </summary>
        /// <param name="talkInfo"></param>
        /// <returns></returns>
        private List<FileInfo> RenderPPT(FileInfo talkInfo)
        {
            string name = talkInfo.Name.Substring(0, talkInfo.Name.IndexOf("."));
            FileInfo pdfFile = new FileInfo(talkInfo.Directory + "\\" + name + ".pdf");
            lock (this)
            {
                Application powerApp = null;
                Presentation presentation = null;

                if (!pdfFile.Exists)
                {

                    try
                    {
                        powerApp = new Application();
                        //powerApp.Visible = MsoTriState.msoFalse;
                        presentation = powerApp.Presentations.Open(talkInfo.FullName, MsoTriState.msoTrue, MsoTriState.msoFalse, MsoTriState.msoFalse);
                        presentation.ExportAsFixedFormat(pdfFile.FullName,
                            PpFixedFormatType.ppFixedFormatTypePDF,
                            PpFixedFormatIntent.ppFixedFormatIntentPrint,
                            MsoTriState.msoFalse,
                            PpPrintHandoutOrder.ppPrintHandoutHorizontalFirst,
                            PpPrintOutputType.ppPrintOutputSlides,
                            MsoTriState.msoTrue,
                            null,
                            PpPrintRangeType.ppPrintAll,
                            "Test",
                            false,
                            false,
                            true,
                            true,
                            false,
                            System.Reflection.Missing.Value);
                    }
                    finally
                    {
                        if (presentation != null)
                        {
                            presentation.Close();
                        }
                        if (powerApp != null)
                        {
                            powerApp.Quit();
                        }
                    }
                }
            }

            ///
            /// Ok -- we probably have a PDF file -- do it!
            /// 

            return RenderPDF(pdfFile);
        }


        /// <summary>
        /// Render PDF file to jpeg. Turns out that ghost script does the right thing by
        /// default. So just use that.
        /// </summary>
        /// <param name="talkInfo"></param>
        /// <returns></returns>
        private List<FileInfo> RenderPDF (FileInfo talkInfo)
        {
            return RenderWithGS(talkInfo);
        }

        /// <summary>
        /// Render a talk using GhostScript. We can handle all sorts of input - but mostly
        /// PS and PDF.
        /// </summary>
        /// <param name="talkInfo"></param>
        /// <returns></returns>
        private List<FileInfo> RenderWithGS(FileInfo talkInfo)
        {
            ///
            /// Build up teh various strings and files that we are goign to be using.
            /// 

            string talkName = talkInfo.Name.Substring(0, talkInfo.Name.Length - talkInfo.Extension.Length);
            FileInfo tokenInfo = new FileInfo(talkInfo.DirectoryName + "\\" + talkName + "-done.txt");
            FileInfo logInfo = new FileInfo(talkInfo.DirectoryName + "\\" + talkName + "-log.txt");
            FileInfo filelistInfo = new FileInfo(talkInfo.DirectoryName + "\\" + talkName + "-filelist.txt");

            ///
            /// Has the rendering already been done?
            /// 

            if (tokenInfo.Exists && filelistInfo.Exists)
            {
                List<FileInfo> aresult = new List<FileInfo>();
                using (TextReader wr = filelistInfo.OpenText())
                {
                    string line;
                    while ((line = wr.ReadLine()) != null)
                    {
                        aresult.Add(new FileInfo(line));
                    }
                    wr.Close();
                }
                return aresult;
            }

            ///
            /// Spec out the process we will start.
            /// 

            ProcessStartInfo sinfo = new ProcessStartInfo();
            sinfo.FileName = ExternalToolLocations.LocationGS;
            sinfo.Arguments = "-sDEVICE=jpeg "
                + "-dJPEGQ=100 "
                + "-r" + DotsPerInch.ToString() + "x" + DotsPerInch.ToString() + " "
                + "-sOutputFile=" + talkName + "-%03d.jpg "
                + "-dBATCH -dNOPAUSE "
                + talkInfo.Name;
            sinfo.CreateNoWindow = true;
            sinfo.RedirectStandardOutput = true;
            sinfo.UseShellExecute = false;
            sinfo.WorkingDirectory = talkInfo.DirectoryName;

            ///
            /// Now start the process. We switch the error mode
            /// so that when gs crashes it doesn't pop the dialog
            /// box. And because this is a process wide setting, we need
            /// to make sure two people don't try to start the process
            /// at once.
            /// 

            DateTime startime = DateTime.Now;
            Process gsproc = null;

            lock (this)
            {
                using (ChangeErrorMode newerr = new ChangeErrorMode(ChangeErrorMode.ErrorModes.FailCriticalErrors | ChangeErrorMode.ErrorModes.NoGpFaultErrorBox))
                {
                    gsproc = Process.Start(sinfo);
                    gsproc.PriorityClass = ProcessPriorityClass.Idle;
                }
            }

            ///
            /// Now monitor the i/o that comes back from there.
            /// 

            string error_seen = "";
            List<FileInfo> result = new List<FileInfo>();

            using (TextWriter wr = logInfo.CreateText())
            {
                wr.WriteLine("Converting PDF to jpeg");
                wr.WriteLine("ghost script path: " + sinfo.FileName);
                wr.WriteLine("Arguments: " + sinfo.Arguments);
                wr.WriteLine("Log:");

                string line = "hi";
                while ((line = gsproc.StandardOutput.ReadLine()) != null)
                {
                    line = line.Trim();
                    wr.WriteLine(line);
                    if (line.StartsWith("Error:"))
                    {
                        error_seen = line;
                    }
                }
                wr.Close();
            }

            ///
            /// Be good, wait for it to shut down - which should be seconds
            /// 

            gsproc.WaitForExit();
            DateTime endtime = DateTime.Now;

            ///
            /// Find all the output jpeg images by scanning the directory.
            /// 

            int page_number = 1;
            bool done = false;
            while (!done)
            {
                FileInfo fn = new FileInfo(talkInfo.DirectoryName + "\\" + talkName + "-" + page_number.ToString("000") + ".jpg");
                done = !fn.Exists;
                if (!done)
                {
                    result.Add(fn);
                }
                page_number = page_number + 1;
            }

            ///
            /// And create the token file, but only if there was no error processing it!
            /// 

            if (error_seen == "")
            {
                using (TextWriter wr = tokenInfo.CreateText())
                {
                    wr.WriteLine("Started: " + startime.ToShortTimeString());
                    wr.WriteLine("Done: " + endtime.ToShortTimeString());
                    wr.Close();
                }

                /// Cache the filelist for later use!

                using (TextWriter wr = filelistInfo.CreateText())
                {
                    foreach (var fname in result)
                    {
                        wr.WriteLine(fname);
                    }
                    wr.Close();
                }

                return result;
            }

            ///
            /// We had an error - bug out
            /// 

            Logger.LogErrorMessage("GSFail", "Failed to render " + talkInfo.FullName);

            return new List<FileInfo>();
        }

        /// <summary>
        /// Look at all talks and extract them all! :-)
        /// </summary>
        /// <param name="finalMeeting"></param>
        /// <returns></returns>
        public Dictionary<Talk, List<FileInfo> > RenderMeeting(IndicoInterface.SimpleAgendaDataModel.Meeting finalMeeting)
        {
            var talks = from s in finalMeeting.Sessions
                        from t in s.Talks
                        select t;

            var renderedTalks = from t in talks
                                select new
                                {
                                    Talk = t,
                                    Slides = RenderTalk(t.SlideURL)
                                };

            Dictionary<Talk, List<FileInfo>> result = new Dictionary<Talk, List<FileInfo>>();
            foreach (var rt in renderedTalks)
            {
                result[rt.Talk] = rt.Slides;
            }

            return result;
        }
    }
}
